using System;
using System.Data;
using System.Collections.Generic;
using System.Globalization;
using System.Text;

using Kenly.DBFramework.Gateway;

namespace Kenly.DBFramework.Reflection
{
    /// <summary>
    /// Mapping the properties to sql statements .
    /// </summary>
    internal abstract class CommandReflector : ICommandReflector
    {

        #region Common

        /// <summary>
        /// 
        /// </summary>
        /// <param name="entity"></param>
        protected static void ValidateEntity(object entity)
        {
            if (entity == null)
            {
                throw new GatewayException(MsgResource.EmptyEntity);
            }
            if (string.IsNullOrEmpty(AttributeUtility.ReflectTableName(entity.GetType())))
            {
                throw new GatewayException(MsgResource.InvalidEntityConfig, entity);
            }
        }

        protected static void ValidateType(Type type)
        {
            if (type == null)
            {
                throw new GatewayException(MsgResource.EmptyEntity);
            }
            if (string.IsNullOrEmpty(AttributeUtility.ReflectTableName(type)))
            {
                throw new GatewayException(MsgResource.InvalidEntityConfig, type);
            }
        }

        private static Expression BuildExpressionById(object entity)
        {
            string identityName = null;
            object identityValue = null;
            IdentityUtility.GetIdentity(entity, out identityName, out identityValue);
            if (string.IsNullOrEmpty(identityName))
            {
                throw new GatewayException(MsgResource.IdentityOfEntityMissing, entity);
            }

            return Expression.Equal(identityName, identityValue);
        }

        private static Expression BuildExpressionByPK(object entity)
        {
            string key = null;
            object keyValue =null;
            KeyUtility.GetPrimaryKey(entity, out key, out keyValue);
            if (string.IsNullOrEmpty(key))
            {
                throw new GatewayException(MsgResource.PrimaryKeyMissing, entity);
            }
            return Expression.Equal(key, keyValue);
        }


        #endregion

        //protected static Order GetPagingOrder(Type entityType, Order order)
        //{
        //    if (entityType == null)
        //    {
        //        throw new GatewayException(MsgResource.EmptyEntity);
        //    }
        //    string orderColumn = null;
        //    Order pagingOrder = null;
        //    if (order != null && !string.IsNullOrEmpty(order.OrderColumn))
        //    {
        //        pagingOrder = order;
        //    }
        //    else
        //    {
        //        orderColumn = IdentityUtility.GetIdentityName(entityType);
        //        if (!string.IsNullOrEmpty(orderColumn))
        //        {
        //            pagingOrder = new Order(orderColumn, OrderDirection.Descend);
        //        }
        //        else
        //        {
        //            orderColumn = KeyUtility.GetPrimaryKeyName(entityType);
        //            if (string.IsNullOrEmpty(orderColumn))
        //            {
        //                throw new GatewayException(MsgResource.IDAndPKOfEntityMissing, entityType);
        //            }
        //            else
        //            {
        //                pagingOrder = new Order(orderColumn, OrderDirection.Descend);
        //            }
        //        }
        //    }
        //    return pagingOrder;
        //}


        #region ICommandReflection Members




        /// <summary>
        /// Reflect delete statement from entity's properites . 
        /// Format : DELETE FROM TABLE WHERE ID=Value
        /// </summary>
        /// <param name="entityType"></param>
        /// <param name="condition"></param>
        /// <returns></returns>
        public string Delete(Type entityType, string condition)
        {
            ValidateType(entityType);
            if (string.IsNullOrEmpty(condition))
            {
                condition = " null=null ";
            }

            string tableName = AttributeUtility.ReflectTableName(entityType);
            return string.Concat("DELETE  FROM ", tableName, " WHERE ", condition);
        }

        public string DeleteById(object entity)
        {
            Expression exp = BuildExpressionById(entity);
            return Delete(entity.GetType(), exp);
        }

        public string DeleteByPK(object entity)
        {
            Expression exp = BuildExpressionByPK(entity);
            return Delete(entity.GetType(), exp);
        }


        public abstract string Exists(Type entityType, string condition);

        public string ExistsById(object entity)
        {
            Expression exp = BuildExpressionById(entity);
            return this.Exists(entity.GetType(), exp);
        }

        public string ExistsByPK(object entity)
        {
            Expression exp = BuildExpressionByPK(entity);
            return this.Exists(entity.GetType(), exp);
        }


        public string Count(Type entityType, string condition)
        {
            ValidateType(entityType);
            if (string.IsNullOrEmpty(condition))
            {
                condition = " null=null ";
            }
            return string.Concat("SELECT COUNT(*) FROM ", AttributeUtility.ReflectTableName(entityType), " WHERE ", condition);
        }

        public string CountGroup(Type entityType, string condition, string groupBy)
        {
            string sql = this.Count(entityType, condition);

            if (!string.IsNullOrEmpty(groupBy))
            {
                sql += " GROUP BY " + groupBy;
            }

            return sql;
        }


        public string Sum(Type entityType, string columnName, string condition)
        {
            ValidateType(entityType);
            if (string.IsNullOrEmpty(condition))
            {
                condition = " null=null ";
            }
            return string.Concat("SELECT SUM(", columnName, ") FROM ",
                AttributeUtility.ReflectTableName(entityType), " WHERE ", condition);
        }

        public string SumGroup(Type entityType, string columnName, string condition, string groupBy)
        {
            string sql = this.Sum(entityType, columnName, condition);

            if (!string.IsNullOrEmpty(groupBy))
            {
                sql += " GROUP BY " + groupBy;
            }

            return sql;
        }

        public string Max(Type entityType, string columnName, string condition)
        {
            ValidateType(entityType);
            if (string.IsNullOrEmpty(condition))
            {
                condition = " null=null ";
            }
            return string.Concat("SELECT MAX(", columnName, ") FROM ",
                AttributeUtility.ReflectTableName(entityType), " WHERE ", condition);
        }

        public string MaxGroup(Type entityType, string columnName, string condition, string groupBy)
        {
            string sql = this.Max(entityType, columnName, condition);

            if (!string.IsNullOrEmpty(groupBy))
            {
                sql += " GROUP BY " + groupBy;
            }

            return sql;
        }

        public string Min(Type entityType, string columnName, string condition)
        {
            ValidateType(entityType);
            if (string.IsNullOrEmpty(condition))
            {
                condition = " null=null ";
            }
            return string.Concat("SELECT MIN(", columnName, ") FROM ",
                AttributeUtility.ReflectTableName(entityType), " WHERE ", condition);
        }

        public string MinGroup(Type entityType, string columnName, string condition, string groupBy)
        {
            string sql = this.Min(entityType, columnName, condition);

            if (!string.IsNullOrEmpty(groupBy))
            {
                sql += " GROUP BY " + groupBy;
            }

            return sql;
        }

        /// <summary>
        /// Format : UPDATE TABLE SET C1=@C1,C2=@C2 WHERE ID=@ID
        /// </summary>
        /// <param name="entityType"></param>
        /// <param name="updatedByPK"></param>
        /// <param name="condition"></param>
        /// <param name="propertyNames"></param>
        /// <returns></returns>
        private string Update(Type entityType, bool updatedByPK, string condition, List<string> propertyNames)
        {
            ValidateType(entityType);
            if (propertyNames == null || propertyNames.Count <= 0)
            {
                throw new GatewayException(MsgResource.InvalidArguments, "List<string> propertyNames");
            }
            if (string.IsNullOrEmpty(condition))
            {
                condition = " null=null ";
            }

            string tableName = AttributeUtility.ReflectTableName(entityType);
            return string.Concat("UPDATE ", tableName, " SET " + GetUpdateColumnParameterPairs(entityType, updatedByPK, propertyNames),
            " WHERE ", condition);
        }

        public string Update(Type entityType, string condition, List<string> propertyNames)
        {
            return this.Update(entityType, false, condition, propertyNames);
        }

        public string UpdateById(object entity, List<string> propertyNames)
        {
            Expression exp = BuildExpressionById(entity);
            return this.Update(entity.GetType(), false, exp, propertyNames);
        }

        public string UpdateByPK(object entity, List<string> propertyNames)
        {
            Expression exp = BuildExpressionByPK(entity);
            return this.Update(entity.GetType(), true, exp, propertyNames);
        }

        protected abstract string GetUpdateColumnParameterPairs(Type entityType, bool updatedByPK, List<string> propertyNames);




        public string SelectByCondition(Type entityType, string condition)
        {
            if (string.IsNullOrEmpty(condition))
            {
                condition = " null=null ";
            }
            ValidateType(entityType);
            string tableName = AttributeUtility.ReflectTableName(entityType);
            return string.Concat("SELECT ", ColumnMapper.SelectAllColumnNameList(entityType), "  FROM ", tableName, " WHERE ", condition);
        }

        public string SelectByCondition(Type entityType, string condition, Order order, List<string> propertyNames)
        {
            ValidateType(entityType);
            if (string.IsNullOrEmpty(condition))
            {
                condition = " null=null ";
            }
            StringBuilder sql = new StringBuilder();
            string tableName = AttributeUtility.ReflectTableName(entityType);

            sql.Append("SELECT ");
            if (propertyNames == null || propertyNames.Count <= 0)
            {
                sql.Append(ColumnMapper.SelectAllColumnNameList(entityType));
            }
            else
            {
                sql.Append(ColumnMapper.SelectColumnNameList(entityType, propertyNames));
            }
            sql.Append(" FROM " + tableName);
            sql.Append(" WHERE " + condition);
            if (order != null && !string.IsNullOrEmpty(order.OrderString))
            {
                sql.Append(" ORDER BY " + order.OrderString);
            }
            return sql.ToString();
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="entityType"></param>
        /// <param name="columnName"></param>
        /// <param name="condition"></param>
        /// <param name="direction"></param>
        /// <returns></returns>
        public string SelectDistinctByCondition(Type entityType, string columnName, string condition, OrderDirection direction)
        {
            string allColumn = "*";

            if (string.IsNullOrEmpty(condition) || string.IsNullOrEmpty(columnName))
            {
                condition = " null=null ";
                columnName = allColumn;
            }

            ValidateType(entityType);
            string tableName = AttributeUtility.ReflectTableName(entityType);
            return string.Concat("SELECT DISTINCT ", columnName,
                "  FROM ", tableName, " WHERE ", condition + GetOrderExpression(columnName, direction));
        }

        private static string GetOrderExpression(string columnName, OrderDirection direction)
        {
            string allColumn = "*";
            if (columnName == allColumn)
            {
                return "";
            }

            return " ORDER BY " + columnName + GetOrderFlag(direction);
        }

        private static string GetOrderFlag(OrderDirection direction)
        {
            return (direction == OrderDirection.Ascend) ? " ASC " : " DESC ";
        }

        public string SelectById(object entity)
        {
            Expression exp = BuildExpressionById(entity);
            return this.SelectByCondition(entity.GetType(), exp);
        }

        public string SelectByPK(object entity)
        {
            Expression exp = BuildExpressionByPK(entity);
            return this.SelectByCondition(entity.GetType(), exp);
        }


        public string SelectByInterrelationTable(object entity, Type interrelationType, string referrenceCondition,
           Order order, List<string> propertyNames)
        {
            ValidateEntity(entity);
            StringBuilder sql = new StringBuilder();

            string primaryKeyName = KeyUtility.GetPrimaryKeyName(entity.GetType());
            if (string.IsNullOrEmpty(primaryKeyName))
            {
                throw new GatewayException(MsgResource.PrimaryKeyMissing, entity);
            }
            if (string.IsNullOrEmpty(referrenceCondition))
            {
                referrenceCondition = " null=null ";
            }

            sql.Append("SELECT ");
            if (propertyNames == null || propertyNames.Count <= 0)
            {
                sql.Append(ColumnMapper.SelectAllColumnNameList(entity.GetType()));
            }
            else
            {
                sql.Append(ColumnMapper.SelectColumnNameList(entity.GetType(), propertyNames));
            }
            sql.Append(" FROM ");
            sql.Append(AttributeUtility.ReflectTableName(entity.GetType()));

            sql.Append(" WHERE " + primaryKeyName);
            string foreignKey = KeyUtility.GetForeignKey(interrelationType, entity.GetType());
            if (string.IsNullOrEmpty(foreignKey))
            {
                foreignKey = primaryKeyName;
            }
            sql.Append(" IN ( SELECT " + foreignKey + " FROM " + AttributeUtility.ReflectTableName(interrelationType));
            sql.Append(" WHERE " + referrenceCondition + " )");
            if (order != null && !string.IsNullOrEmpty(order.OrderString))
            {
                sql.Append(" ORDER BY " + order.OrderString);
            }

            return sql.ToString();
        }

        #endregion


        /// <summary>
        /// Reflect insert statement from entity's properites . Format : INSERT INTO TABLE(C1,C2) VALUES(@C1,@C2)
        /// </summary>
        /// <param name="entityType"></param>
        /// <param name="returnIdentity"></param>
        /// <param name="propertyNames"></param>
        /// <returns></returns>
        public abstract string Insert(Type entityType, bool returnIdentity, List<string> propertyNames);

        public abstract string SelectNextIdentity(Type entityType);

        public abstract string SelectPaging(Type entityType, string condition, Order order, int pageSize, int pageIndex, List<string> propertyNames);

        public abstract string SelectTop(Type entityType, string condition, int topCount, Order order, List<string> propertyNames);
    }
}
